/*
 * Serdes.c
 *
 *  Created on: Oct 14, 2018
 *      Author: ax
 */

#include <Hw/Serdes.h>

#include <ti/csl/csl_bootcfgAux.h>


static SerdesRegs* const hSerdes[] = {
      (SerdesRegs*) &hBootCfg->SRIO_SERDES_CFGPLL,
      (SerdesRegs*) &hBootCfg->SGMII_CFGPLL,
      (SerdesRegs*) &hBootCfg->PCIE_CFGPLL,
      (SerdesRegs*) &hBootCfg->VUSR_CFGPLL };


void
Serdes_config(
    Serdes      module,
    SerdesCfg   cfg,
    LaneMask    master
){
    Uint16 l;      //lane
    Bool   sgmiiFixed = (module == Serdes_SGMII);
    Uint32 clkKhz = sgmiiFixed ? Serdes_2p5G_BPS_KVAL : Serdes_12p5G_BPS_KVAL;

    /*calculate some PLL parameters according to other input parameters
     * SGMII use fixed configuration */
    for (l = 0; l < Serdes_LANE_MAX; l++) { /*find lowest link speed*/
        if ((cfg->lane[l]) && (module != Serdes_SGMII))
            if (cfg->lane[l]->linkSpeed_Kbps < clkKhz)
                clkKhz = cfg->lane[l]->linkSpeed_Kbps;
    }
    /*Serdes PLL output must be between 1.5625 and 3.125*/
    if (clkKhz > Serdes_6p25G_BPS_KVAL)
        clkKhz >>= 2; // divided by 4;
    else if (clkKhz > Serdes_3p125G_BPS_KVAL)
        clkKhz >>= 1; // divided by 2;
    else if(clkKhz < Serdes_1p5625G_BPS_KVAL)
        clkKhz <<= 1; // *= 2;
    cfg->serdesPllClock_KHz = clkKhz;

    SerdesVcoRange vco = cfg->vcoRange;
    if (Serdes_PLL_VCO_RANGE_NOT_SPECIFIED == vco)
        vco = clkKhz < Serdes_2p17G_BPS_KVAL
                ? Serdes_PLL_VCO_RANGE_HIGH : Serdes_PLL_VCO_RANGE_LOW;

    Uint16 pllMpy = clkKhz / cfg->inputRefClock_KHz;
    cfg->pllMpy = pllMpy = (pllMpy < 4 || pllMpy > 60) ? (pllMpy * 4) : (4 * 4);

    CSL_BootCfgUnlockKicker();
    SerdesRegs* hs = hSerdes[module];
    for (l = 0; l < Serdes_LANE_MAX; l++) {
        if (NULL == cfg->lane[l]) continue;
        //else
        SerdesLinkRate linkRateScale;
        if (sgmiiFixed) {
            linkRateScale = SGMII_Serdes_LINK_RATE_div2;
            cfg->lane[l]->rxTermination = Serdes_RX_TERM_SGMII_SPECIFIED;
        }
        else  {
            linkRateScale = Serdes_LINK_RATE_x4;

            Uint16 csale = cfg->lane[l]->linkSpeed_Kbps / clkKhz;
            if (csale < 1) linkRateScale = Serdes_LINK_RATE_div2;
            else if (csale < 2) linkRateScale = Serdes_LINK_RATE_x1;
            else if (csale < 4) linkRateScale = Serdes_LINK_RATE_x2;
        }

        /*Serdes Loopback is controled by Hyperlink control
            register, Serdes loopback register has no effect*/
        /*HyperLink automatically sets LOS to 0 for loopback.
            User must set LOS to 4 for non-loopack*/
        if ((module == Serdes_VUSR) && (cfg->lane[l]->loopBack))
            cfg->lane[l]->rxLos = Serdes_RX_LOS_VUSR_NON_LOOPBACK_MODE;

        hs->LANE[l].CFGRX = (1 << Serdes_RX_CFG_ENRX_SHIFT)
               /*Bus width must be 010b (20 bit) if !SGMII */
            | ((sgmiiFixed ? 0 : 2) << Serdes_RX_CFG_BUSWIDTH_SHIFT)
            | (cfg->lane[l]->testPattern << Serdes_RX_CFG_TESTPATTERN_SHIFT)
            | (cfg->lane[l]->loopBack << Serdes_RX_CFG_LOOPBACK_SHIFT)
            | (1 << Serdes_RX_CFG_ENOC_SHIFT) /*Enable offset compensation*/
            | (cfg->lane[l]->rxEqualizerConfig << Serdes_RX_CFG_EQ_SHIFT)
            | (cfg->lane[l]->rxCDR << Serdes_RX_CFG_CDR_SHIFT)
            | (cfg->lane[l]->rxLos << Serdes_RX_CFG_LOS_SHIFT)
            | (cfg->lane[l]->rxAlign << Serdes_RX_CFG_ALIGN_SHIFT)
            | (cfg->lane[l]->rxTermination << Serdes_RX_CFG_TERM_SHIFT)
            | (cfg->lane[l]->rxInvertPolarity << Serdes_RX_CFG_INVPAIR_SHIFT)
            | (linkRateScale << Serdes_RX_CFG_RATE_SHIFT);

        Uint32 ext = (!sgmiiFixed) ? 0
                : ( (1 << Serdes_SGMII_TX_CFG_MYSNC_SHIFT)
                  | (0 << Serdes_SGMII_TX_CFG_DEMPHASIS_SHIFT)
                  | (1 << Serdes_SGMII_TX_CFG_CM_SHIFT)/*Common mode adjustment*/
                  );
        hs->LANE[l].CFGTX = (1 << Serdes_TX_CFG_ENTX_SHIFT) | ext
            | ((sgmiiFixed ? 0 : 2) << Serdes_TX_CFG_BUSWIDTH_SHIFT) /*Bus width must be 010b (20 bit)*/
            | (cfg->lane[l]->testPattern << Serdes_TX_CFG_TESTPATTERN_SHIFT)
            | (cfg->lane[l]->loopBack << Serdes_TX_CFG_LOOPBACK_SHIFT)
            | ((sgmiiFixed ? 0 : 1) << Serdes_TX_CFG_FIRUPT_SHIFT) /*FIRUPT = 1 Transmitter pre and post cursor FIR filter update*/
            /*TWPST1: Adjacent Post Cursor Tap Weight.
                 If trace length is 4", start with 20 (-10%).
                 If trace length is between 4" and 10", start with 27 (-27.5%).*/
            | ((sgmiiFixed ? 0 : 18) << Serdes_TX_CFG_TWPST1_SHIFT)
            | ((sgmiiFixed ? 0 :  1) << Serdes_TX_CFG_TWPRE_SHIFT) /*TWPRE: The settings range from 0 to -17.5% in 2.5% steps.*/
            | (cfg->lane[l]->txOutputSwing << Serdes_TX_CFG_SWING_SHIFT)
            | (cfg->lane[l]->txInvertPolarity << Serdes_TX_CFG_INVPAIR_SHIFT)
            | (linkRateScale << Serdes_TX_CFG_RATE_SHIFT)
            | (((master & (_BIT(l))) ? 1 : 0) << Serdes_TX_CFG_MYSNC_SHIFT); //master lane is Synchronization as master
    }

    hs->CFGPLL = (1 << Serdes_PLL_CFG_ENPLL_SHIFT)
            | (cfg->loopBandwidth << Serdes_PLL_CFG_LOOPBANDWIDTH_SHIFT)
            | (vco << Serdes_PLL_CFG_VRANGE_SHIFT)
            | (pllMpy << Serdes_PLL_CFG_MPY_SHIFT);

    CSL_BootCfgLockKicker();
}


bool
Serdes_isLocked(
    Serdes module
){
    RegPtr sts = &hBootCfg->STS_SRIO;
    return *(sts + module) & Serdes_PLL_LOCKED_BIT;
}
